﻿using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Core;
using System.Data.Entity.Core.EntityClient;
using System.Data.Entity.Core.Objects;
using System.Data.Entity.Core.Objects.DataClasses;
using System.Data.SqlClient;
using System.Linq;
using System.Linq.Expressions;
using EntityFrameworkExtension.Interface;
using log4net;

namespace EntityFrameworkExtension
{
    public class EfEntities :  IEfContext
    {
        private enum ColumnName
        {
            LastModifiedOn
        }

        private readonly ILog logger = LogManager.GetLogger(typeof(EfEntities));

        private readonly SaveChangesHandler saving = new SaveChangesHandler();

        private EFModelContainer ModelContainerEntity { get; set; }

        public int CommandTimeout 
        {
            set
            {
                if (ModelContainerEntity != null)
                    ModelContainerEntity.CommandTimeout = value;
            }
        }

        public IEfContext Open(string server, string database)
        {
            if (ModelContainerEntity != null)
                throw new ApplicationException("HeimdallEnity is not null. Initialising the entityFramework Failed");
             var connection = new EntityConnectionStringBuilder()
                                 {
                                     Metadata =
                                         @"res://EFModel/EFModel.csdl|res://EFModel/EFModel.ssdl|res://EFModel/EFModel.msl",
                                     Provider = @"System.Data.SqlClient",
                                     ProviderConnectionString = new SqlConnectionStringBuilder()
                                                                    {
                                                                        DataSource = server,
                                                                        InitialCatalog = database,
                                                                        IntegratedSecurity = true,


                                                                    }.ConnectionString,
                                 };
             ModelContainerEntity = new EFModelContainer(connection.ConnectionString);
            ModelContainerEntity.SavingChanges += saving.HeimdallEntitySavingChanges;
            return this;
        }


        public bool HasChanges(EntityKey entityKey)
        {
            var entry = ModelContainerEntity.ObjectStateManager.GetObjectStateEntry(entityKey);
            if (entry.State == EntityState.Added) 
                return true;

            if (entry.State == EntityState.Modified)
            {
                var current = entry.CurrentValues;
                var original = entry.OriginalValues;
                var hasChanges = entry.GetModifiedProperties().Where(y => y != ColumnName.LastModifiedOn.ToString()).Any(x => !current[x].Equals(original[x]));
                return hasChanges;

            }
            return false;

        }

        public List<T> GetObjectList<T>(Expression<Func<T, bool>> predicate) where T : class
        {
            IQueryable<T> set = ModelContainerEntity.CreateObjectSet<T>();
            set = set.Where(predicate);
            return set.ToList();
        }
       
        public List<T> GetObjectList<T>() where T : class
        {
            var set = ModelContainerEntity.CreateObjectSet<T>();
            return set.Select(x => x).ToList();
        }
        
        public void AddToObjectList<T>(T item) where T : class
        {
            var set = ModelContainerEntity.CreateObjectSet<T>();
            set.AddObject(item);
        }

        public void Delete<T>(T item) where T :class
        {
            var set = ModelContainerEntity.CreateObjectSet<T>();
            set.DeleteObject(item);
        }

        public void Dispose()
        {
            if (ModelContainerEntity != null)
            {
                ModelContainerEntity.SavingChanges -= saving.HeimdallEntitySavingChanges;
                ModelContainerEntity.Dispose();
                ModelContainerEntity = null;
            }
        }

        public void SaveChanges()
        {
            ModelContainerEntity.SaveChanges();
        }


       

        public List<TEntity> SelectCurrent<TEntity>(Expression<Func<TEntity, bool>> filter,
            Func<IQueryable<TEntity>,
            IOrderedQueryable<TEntity>> orderBy) where TEntity : class
        {
            IQueryable<TEntity> query = ModelContainerEntity.CreateObjectSet<TEntity>();

            if (filter != null)
            {
                query = query.Where(filter);
            }


            return orderBy != null ? orderBy(query).ToList() : query.ToList();
        }


        public List<TEntity> SelectCurrent<TEntity>(List<string> includes) where TEntity : class
        {
            ObjectQuery<TEntity> query = ModelContainerEntity.CreateObjectSet<TEntity>();

            includes.ForEach(x => query = query.Include(x));

            return query.ToList();
        }

        public List<TEntity> SelectCurrent<TEntity>(string include) where TEntity : class
        {
            return SelectCurrent<TEntity>(new List<string> { include });
        }

        public List<TEntity> SelectCurrent<TEntity>() where TEntity : class
        {
            return SelectCurrent<TEntity>(Enumerable.Empty<string>().ToList());
        }

        public TEntity First<TEntity>(Expression<Func<TEntity, bool>> filter) where TEntity : class
        {
            IQueryable<TEntity> query = ModelContainerEntity.CreateObjectSet<TEntity>();

            query = query.Where(filter);

            return query.First();
        }


        public TEntity FirstOrDefault<TEntity>(Expression<Func<TEntity, bool>> filter) where TEntity : class
        {
            IQueryable<TEntity> query = ModelContainerEntity.CreateObjectSet<TEntity>();

            query = query.Where(filter);

            return query.FirstOrDefault();
        }


        public List<TEntity> WhereIn<TEntity, TValue>(Expression<Func<TEntity, TValue>> filter,
            IEnumerable<TValue> collection) where TEntity : class
        {
            IQueryable<TEntity> query = ModelContainerEntity.CreateObjectSet<TEntity>();

            var queryObject = query as ObjectQuery<TEntity>;
            query = queryObject.WhereIn(filter, collection);
            return query.ToList();
        }

        public List<TEntity> SelectCurrent<TEntity>(Expression<Func<TEntity, bool>> filter) where TEntity : class
        {
            return SelectCurrent(filter, Enumerable.Empty<string>().ToList());
        }

        public bool IsDirty
        {
            get
            {
                return
                    ModelContainerEntity.ObjectStateManager.GetObjectStateEntries(EntityState.Added |
                                                                                  EntityState.Deleted |
                                                                                  EntityState.Modified).Any();
            }
        }

        public List<TEntity> SelectCurrent<TEntity>(Expression<Func<TEntity, bool>> filter, List<string> includes) where TEntity : class
        {
            IQueryable<TEntity> query = ModelContainerEntity.CreateObjectSet<TEntity>();

            var queryObject = query as ObjectQuery<TEntity>;

            includes.ForEach(x => queryObject = queryObject.Include(x));

            query = queryObject.Where(filter);

            return query.ToList();
        }

        public void Refresh<TEntity>(RefreshMode storeWins, TEntity entity) where TEntity : class
        {
            ModelContainerEntity.Refresh(storeWins, entity);
        }

        public void Detach(object entity)
        {
            ModelContainerEntity.Detach(entity);
        }

        public void Attach(IEntityWithKey entity)
        {
            ModelContainerEntity.Attach(entity);
        }


    
    }


    public static class ExtensionMethods
    {
        public static IQueryable<TEntity> WhereIn<TEntity, TValue>
            (
            this ObjectQuery<TEntity> query,
            Expression<Func<TEntity, TValue>> selector,
            IEnumerable<TValue> collection
            )
        {
            if (selector == null) throw new ArgumentNullException("selector");
            if (collection == null) throw new ArgumentNullException("collection");
            if (!collection.Any())
                return query.Where(t => false);

            ParameterExpression p = selector.Parameters.Single();

            IEnumerable<Expression> equals = collection.Select(value =>
                (Expression) Expression.Equal(selector.Body,
                    Expression.Constant(value, typeof (TValue))));

            Expression body = equals.Aggregate(Expression.Or);

            return query.Where(Expression.Lambda<Func<TEntity, bool>>(body, p));
        }

        //Optional - to allow static collection:
        public static IQueryable<TEntity> WhereIn<TEntity, TValue>
            (
            this ObjectQuery<TEntity> query,
            Expression<Func<TEntity, TValue>> selector,
            params TValue[] collection
            )
        {
            return WhereIn(query, selector, (IEnumerable<TValue>) collection);
        }
    }

}
